﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TTGame;
using System;

public enum UIRecycleStrategy {
    Position,
    Visible,
}

/// <summary>
/// 对象实例缓存与回收 for UI
///
/// 实例化后放在回收站父节点下。如果使用期间更换了父节点，回收时会自动放回回收站父节点。
/// 实例是否已被回收的状态判断。1、将对象不可见。2、对象本地坐标Y大于9999
///
/// add：
/// 添加最大数量限制。
///
/// </summary>
namespace TTGameEngine {
public class UIObjectRecycler : MonoBehaviour {
    #region Fields

    public Transform xForm;

    public float maxParticleDespawnTime = 20f;

    //<Prefab, rootNode>
    public Dictionary<GameObject, Transform> poolParents = new Dictionary<GameObject, Transform>();
    public Dictionary<GameObject, List<GameObject>> poolObjects = new Dictionary<GameObject, List<GameObject>>();
    public Dictionary<GameObject, RecycleInfo> objRecycleInfos = new Dictionary<GameObject, RecycleInfo>();

    public static UIObjectRecycler instance;

    public static void Clear() {
        instance = null;
    }

    #endregion Fields

    #region Properties

    public static UIObjectRecycler Instance {
        get {
            if(instance == null) {
                GameObject go = new GameObject();
                go.name = "UI Recycler";
                go.AddComponent<UIObjectRecycler>();
                instance = go.GetComponent<UIObjectRecycler>();
            }
            return instance;
        }
    }

    #endregion Properties

    #region Methods

    void Awake() {
        instance = this;
        xForm = transform;
    }

    public void Init(Transform parent) {
        //instance.transform.parent = parent;
        instance.transform.SetParent(parent, false);
        instance.transform.localPosition = Vector3.zero;
        instance.transform.localScale = Vector3.one;
    }

    //获得prefab的父节点。
    //true:Prefab和父节点已存在   false:Prefab不存在，父节点是新建的
    Transform ObtainParentRoot(GameObject aPrefab, out bool isExist) {
        Transform parentTF = null;
        if(!poolObjects.ContainsKey(aPrefab)) {
            //每个Prefab创建一个根节点，节点下是相同Prefab的实例
            GameObject poolParent = new GameObject();
#if UNITY_EDITOR
            poolParent.name = "PrefabRoot-" + aPrefab.name;
#endif
            parentTF = poolParent.transform;
            parentTF.localPosition = Vector3.zero;
            parentTF.localScale = Vector3.one;
            poolObjects.Add(aPrefab, new List<GameObject>());
            poolParents.Add(aPrefab, parentTF);
            isExist = false;
        } else {
            parentTF = poolParents[aPrefab];
            isExist = true;
        }
        return parentTF;
    }

    //准备好prefab的父节点
    bool CheckParentRoot(GameObject aPrefab) {
        Transform parentTF = null;
        bool isPrefabExist = true;
        if(!poolObjects.ContainsKey(aPrefab)) {
            //每个Prefab创建一个根节点，节点下是相同Prefab的实例
            GameObject poolParent = new GameObject();
#if UNITY_EDITOR
            poolParent.name = "PreInsPrefabRoot-" + aPrefab.name;
#endif
            parentTF = poolParent.transform;
            parentTF.parent = xForm;
            parentTF.localPosition = Vector3.zero;
            poolObjects.Add(aPrefab, new List<GameObject>());
            poolParents.Add(aPrefab, parentTF);
            isPrefabExist = false;
        }
        return isPrefabExist;
    }

    public GameObject InstantiatePrefab(GameObject aPrefab)
    {
        return InstantiatePrefab(aPrefab, Vector3.zero, Quaternion.identity);
    }

    /// <summary>
    /// Instantiates the prefab.
    /// </summary>
    /// <returns>The prefab.</returns>
    /// <param name="aPrefab">A prefab.</param>
    /// <param name="position">Position.</param>
    /// <param name="rotation">Rotation.</param>
    /// <param name="broadcast">If set to <c>true</c> broadcast.</param>
    /// <param name="isUsePosHide"> 回收标记——隐藏 或者 坐标</param>
    public GameObject InstantiatePrefab(GameObject aPrefab, Vector3 position, Quaternion rotation, bool broadcast = false, bool isUsePosHide = false)
    {
        if(aPrefab == null) return null;
        bool isPrefabExist = false;
        Transform objParentTF = ObtainParentRoot(aPrefab, out isPrefabExist);

        GameObject freeObject = null;
        //Look for free objects
        if(isPrefabExist) {
            if(isUsePosHide == false) {
                freeObject = (from item in poolObjects[aPrefab]
                              where item != null && (item.activeSelf == false)
                              select item).FirstOrDefault();
            } else {
                freeObject = (from item in poolObjects[aPrefab]
                              where item != null && (item.transform.localPosition.y > 9999f)
                              select item).FirstOrDefault();
            }
        }
        if(freeObject == null) {
            freeObject = CreateNewInstance(aPrefab, position, rotation);
            // If set to broadcast Notify instance it was spawned so it can manage it's state
            if(broadcast && freeObject) {
                StartCoroutine(DelayBroadcast(freeObject));
            }
        } else {
            ReInstantiate(freeObject, position, rotation);
            // If set to broadcast Notify instance it was spawned so it can manage it's state
            if(broadcast) {
                freeObject.BroadcastMessage("OnSpawned", SendMessageOptions.DontRequireReceiver);
            }
        }
        if(freeObject != null) {
            AddRecycleInfo(freeObject, aPrefab, objParentTF, isUsePosHide, broadcast);
        }
        return freeObject;
    }

    void AddRecycleInfo(GameObject instanceObj, GameObject aPrefab, Transform aRootTF, bool aIsUsePosHide, bool aBroadCast) {
        RecycleInfo info = null;
        if(objRecycleInfos.TryGetValue(instanceObj, out info)) {
            info.SetInfo(aPrefab, aRootTF, aIsUsePosHide, aBroadCast);
        } else {
            info = RecycleInfo.CreateInfo(aPrefab, aRootTF, aIsUsePosHide, aBroadCast);
            objRecycleInfos.Add(instanceObj, info);
        }

    }

    //mark:test 仅仅是改个名字，用以标记改了哪些代码。！其他人不要用！
    //代表参数Prefab已做过预加载处理，不会为null。
    public Transform ObtainObject(GameObject aPrefab, Vector3 position, Quaternion rotation, bool broadcast = false) {
        return InstantiatePrefab(aPrefab, position, rotation, broadcast).transform;
    }

    public void Deactivate(GameObject instanceObj) {
        Transform instanceTF = instanceObj.transform;
        RecycleInfo info = null;
        objRecycleInfos.TryGetValue(instanceObj, out info);
        if(info == null) {
            DebugUtil.LogErrorFormat("[ObjRecycler]回收对象[{0}]时，没有找到回收信息。", instanceObj.name);
            return;
        }
        if(info.broadCast) { instanceTF.BroadcastMessage("OnDespawned", SendMessageOptions.DontRequireReceiver); }
        //恢复父节点
        if(instanceTF.parent != info.rootNode) {
            instanceTF.transform.SetParent(info.rootNode,false);
        }
        if(info.isUsePosHide == false) {
            instanceTF.gameObject.SetActive(false);
        } else {
            instanceTF.localPosition.SetY(10000f);
        }
    }

    //Re-activate Object
    void ReInstantiate(GameObject aFreeObject, Vector3 position, Quaternion rotation) {
        aFreeObject.transform.position = position;
        aFreeObject.transform.rotation = rotation;
        aFreeObject.SetActive(true);
    }

    //Create Object
    GameObject CreateNewInstance(GameObject aPrefab, Vector3 position, Quaternion rotation, bool active = true, UIRecycleStrategy strategy = UIRecycleStrategy.Visible) {
        if(aPrefab == null) { return null; }

        bool isActive = aPrefab.activeSelf;
        aPrefab.SetActive(active); //实例化一个隐藏的对象，且不触发Awake
        GameObject newObj = Instantiate(aPrefab, position, rotation) as GameObject;
        aPrefab.SetActive(isActive);

        newObj.transform.SetParent(poolParents[aPrefab], false);
        poolObjects[aPrefab].Add(newObj);
#if UNITY_EDITOR
        int childCount = poolObjects[aPrefab].Count;
        newObj.name = aPrefab.name + "_" + childCount;
        poolParents[aPrefab].name = aPrefab.name + "(s) COUNT = " + childCount;
#endif
        if(!active) {
            if(strategy == UIRecycleStrategy.Visible) {
                newObj.SetActive(false);
            } else {
                newObj.transform.localPosition.SetY(10000f);
            }
        }
        return newObj;
    }

    private IEnumerator DelayBroadcast(GameObject aPrefab) {
        //Newly instantiated objects only receive broadcast messages later
        yield return new WaitForEndOfFrame();
        aPrefab.BroadcastMessage("OnSpawned", SendMessageOptions.DontRequireReceiver);
    }

    Queue<InstantiatedPrefab> preInstantiateWaitingList = new Queue<InstantiatedPrefab>();
    //提前克隆 并缓存
    public void PreInstantiateObject(GameObject go, int maxCount, UIRecycleStrategy strategy = UIRecycleStrategy.Visible) {
        if(go && !CheckParentRoot(go)) {
            preInstantiateWaitingList.Enqueue(new InstantiatedPrefab(go, maxCount, strategy));
            if(isPreInsRunning == false) {
                StartCoroutine(PreInstantiateInOrder());
            }
        }
    }

    public bool IsLoading() {
        return preInstantiateWaitingList.Count > 0;
    }

    public void PreInstantiateObject(InstantiatedPrefab info) {
        if(info != null && !CheckParentRoot(info.thePrefab)) {
            preInstantiateWaitingList.Enqueue(info);
            if(isPreInsRunning == false) {
                StartCoroutine(PreInstantiateInOrder());
            }
        }
    }

    bool isPreInsRunning = false;
    long t_lastTime = 0;
    float t_timeCost = 0;
    IEnumerator PreInstantiateInOrder() {
        isPreInsRunning = true;
        InstantiatedPrefab p = null;
        t_timeCost = 0;
        t_lastTime = Tools.getTimeInMUS();
        float t_loadFPSTimeLimit = DebugSetting.Self.LoadFPSTimeLimit;
        while(preInstantiateWaitingList.Count > 0) {
            p = preInstantiateWaitingList.Dequeue();
            //Instantiate
            if(p != null) {
                //DebugUtil.LogFormat("create Instance {0}/{1}", p.thePrefab.name, p.InitialCount);
                for(int i = 0; i < p.InitialCount; i++) {
                    CreateNewInstance(p.thePrefab, Vector3.zero, p.thePrefab.transform.rotation, false, p.strategy);
                    //Wait between instantiations
                    long nowTime = Tools.getTimeInMUS();
                    t_timeCost += Tools.TimeMusToSecond(nowTime - t_lastTime);
                    t_lastTime = nowTime;
                    //DebugUtil.LogFormat("create Instance time:{0}/{1}  [{2}/{3}] dt:{4}", t_timeCost, t_loadFPSTimeLimit, i, p.InitialCount, Time.deltaTime);
                    if(t_timeCost > t_loadFPSTimeLimit) {
                        t_timeCost = 0;
                        //DebugUtil.LogFormat("create Instance. Next Frame");
                        yield return null;
                    }
                }
            }
        }
        isPreInsRunning = false;
    }


    void OnDestroy() {
        instance = null;
        preInstantiateWaitingList.Clear();
    }

    #endregion Methods

    #region Nested Types

    [System.Serializable]
    public class InstantiatedPrefab {
        #region Fields

        float m_initialCount = 1;
        int m_maxInitialCount = 999;

        public GameObject thePrefab;
        public UIRecycleStrategy strategy;

        #endregion Fields

        #region Constructors

        public InstantiatedPrefab(GameObject thePrefab, float initialCount, UIRecycleStrategy aStrategy = UIRecycleStrategy.Visible, int maxCount = 999) {
            this.thePrefab = thePrefab;
            this.m_initialCount = initialCount;
            this.strategy = aStrategy;
            this.m_maxInitialCount = maxCount;
        }
        public InstantiatedPrefab(GameObject thePrefab, int initialCount, UIRecycleStrategy aStrategy = UIRecycleStrategy.Visible, int maxCount = 999) {
            this.thePrefab = thePrefab;
            this.m_initialCount = (float)initialCount;
            this.strategy = aStrategy;
            this.m_maxInitialCount = maxCount;
        }

        #endregion Constructors
        public int InitialCount {
            get {
                return Mathf.Min(Mathf.CeilToInt(m_initialCount), m_maxInitialCount);
            }
        }

        public void IncInitialCount(float count) {
            m_initialCount += count;
        }

        public void SetMaxInitCount(int count) {
            m_maxInitialCount = count;
        }

    }

    #endregion Nested Types
}
}